
package Network;

import Controller.ChatController;
import Controller.Exceptions.ControllerReceivedException;
import Model.ListRequest;
import Model.ListUser;
import Network.Interface.NetworkListenerInterface;
import Network.Exceptions.NetworkException;
import Network.Interface.NetworkSenderInterface;
import Model.Exceptions.LoginException;
import Model.FileId;
import Model.PathFile;
import Model.Status;
import Model.LocalUserLogin;
import UDPTools.Exceptions.UDPListernerException;
import UDPTools.Exceptions.UDPSenderException;
import UDPTools.Interface.UDPListenerInterface;
import UDPTools.Interface.UDPSenderInterface;
import UDPTools.UDPListener;
import UDPTools.UDPSender;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.logging.Level;
import java.util.logging.Logger;
import messages.Bye;
import messages.ChatMessage;
import messages.Composing;
import messages.FileRequest;
import messages.FileResponse;
import messages.Hello;
import messages.Message;
import messages.Presence;
import messages.Wizz;

/**
 * La classe ChatNetwork prend en charge la communication entre les couches de bas niveau
 * ChatController implemente deux interfaces:
 * <ul>
 * <li>NetworkListenerInterface pour la communication avec UDPListener
 * <li>NetworkSenderInterface pour la communication avec  UDPSender
 * </ul>
 * <p>Les methodes du ChatNetwork chargées de la communications avec UDPSender
 * lèvent une exception de type  <tt>NetworkException</tt>
 * 
 * @author      Frank Djomo
 * @author      Christelle SIAGO HAPPI
 * @author      djomopoueme@gmail.com
 * @author      christelle.siagohappi@gmail.com
 * @version     1.0
 * @see     UDPListener
 * @see     UDPSender
 * @see     ChatController
 */
public class ChatNetwork implements NetworkListenerInterface,NetworkSenderInterface{

    private UDPSenderInterface udpSender;
    private UDPListenerInterface udpListener;
    private ChatController controller;

    private   ListUser   mListUser;
    private   ListRequest fileReceived;
    private   ListRequest  fileSended;
    private   LocalUserLogin lUser;
    /**
    * Contructeur
    * @param local     Port d'ecoute du systeme
    * @param distant   Port d'ecoute des systèmes distants
    * @param envoi     Port d'envoi des messages
    * @throws UDPSenderException
    */   
    public ChatNetwork(int local,int distant, int envoi) throws NetworkException {
        try{
            udpSender = new UDPSender(envoi,distant);
            udpListener= new UDPListener(this,local);
            udpListener.StartListening();

            mListUser = new ListUser();
            fileReceived =new ListRequest();
            fileSended = new ListRequest();
        } catch (UDPListernerException ex) {
            Logger.getLogger(ChatNetwork.class.getName()).log(Level.SEVERE, null, ex);
            throw new NetworkException("Erreur lors de la creation UDPListener");
        } catch (UDPSenderException ex) {
            Logger.getLogger(ChatNetwork.class.getName()).log(Level.SEVERE, null, ex);
            throw new NetworkException("Erreur lors de la creation UDPSender");
        }
    }

    /**
    * Affecte un ChatController à notre ChatNetwork
    * @param controller ChatController à affecter
    */
    public final void setController(ChatController controller) {
        this.controller = controller;
    }
    /**
    * Gère le traitement des messages reçus 
    * @param message Message recu d'un utilisateur distant
    * @param adress  Adresse IP de l'utilisateur distant
    * @throws NetworkException 
    */
    @Override
    public void receivedMessage(Message message, InetAddress adress) throws NetworkException {
    // Reception d'un signal Hello
        if(message instanceof Hello)
        {    
            try {
                Hello hello = (Hello) message;
                String remoteLogin = hello.getUsername();
                if(!remoteLogin.equalsIgnoreCase(lUser.getUsername())){ //Ne pas rajouter l'utilisateur local
                    mListUser.addElement(hello.getUsername(),adress);
                    controller.receivedHello(remoteLogin,hello.getWaitAck(),adress);
                }
            } catch (ControllerReceivedException ex) {
                Logger.getLogger(ChatNetwork.class.getName()).log(Level.SEVERE, null, ex);
                throw new NetworkException("Erreur lors de la reception d'un Hello",ex);
            }
        }
         //Reception d'un signal GoodBye
         else if(message instanceof Bye)
         {
                Bye goodBye = (Bye) message;
                String rName=goodBye.getUsername();
                System.out.println("Deconnexion du remoteUser :"+rName+"@"+mListUser.getAddressByName(rName));
                mListUser.removeElement(rName);
                controller.receivedBye(rName);
         }
         //Reception d'un signal TextMessage
         else if(message instanceof ChatMessage)
         {
                ChatMessage messageRecu = (ChatMessage) message;
                String rUser = mListUser.getUsernameByAddress(adress);
                System.out.println("Reception d'une message de "+rUser+"@"+adress+": "+messageRecu.toString());
                controller.receivedChat(messageRecu.getMsgToSend(), rUser);
         }
         //Reception d'un signal FileRequest
         else if(message instanceof FileRequest)
         {
                FileRequest request = (FileRequest) message ;
                String rUser=request.getUsername();
                fileReceived.addElement(rUser, request);
                System.out.println("Demande de transfert de fichier de :"+rUser+"@"+adress+"FileID :"+request.getFileId()+"\n");

                controller.receivedFileRequest(request.getName(),rUser);
         }     
         //Reception d'un signal FileResponse
         else if(message instanceof FileResponse)
         {
            FileResponse response = (FileResponse) message ;
            String rUser= response.getUsername();
            FileRequest  request = fileSended.getRequestByName(rUser);
            System.out.println("Reponse transfert de fichier de :"+rUser
                           +"@"+adress+"\n Reponse :"+response.haveAccept());
             if(request.getFileId().equals(response.getFileId()) && response.haveAccept()){
                     this.sendFileData(adress,response.getPortReception(),request.getName());
                     fileSended.removeElement(rUser);
                     controller.receivedFileResponse(response.haveAccept());

             }
         } 
         //Reception d'un wizz
         else if(message instanceof Wizz)
         {
                Wizz wizzer = (Wizz)message;
                controller.receivedWizzer(wizzer.getUsername());
         }
         //Reception notification de statut
         else if(message instanceof Presence)
         {
                Presence presence = (Presence)message;
                controller.receivedStatus(presence.getUsername(),presence.getStatus());
         }
         //Reception notification de statut
         else if(message instanceof Composing)
         {
                Composing compose = (Composing)message;
                controller.receivedComposing(compose.getUsername(),compose.isIsWriting());
         }
    }
    /**
    * Gère l'envoi d'un signal Hello pour la notification de connexion.
    * Lors de la connexion d'un utilisateur, un message <i>Hello Request</i> est diffusé en
    * broadcast sur le réseau pour notifier la connexion aux autres utilisateurs.
    * L'utilisateur attend un Hello Reply pour confirmer la notification.
    * @see Hello
    * @param login     Login de l'utilisateur local
    * @param request   Demande de réponse <code>true</code> 
    * @throws NetworkException 
    */
    @Override
    public void sendHello(String login,boolean request) throws NetworkException {
        try {
            lUser = new LocalUserLogin(login);
            udpSender.sendHello(login, request);
            System.out.println("connexion du localUser "+lUser.getUsername());
        } catch (UDPSenderException ex) {
            Logger.getLogger(ChatNetwork.class.getName()).log(Level.SEVERE, null, ex);
            throw new NetworkException("Erreur lors du sendHello",ex);
        } catch (LoginException ex) {
            Logger.getLogger(ChatNetwork.class.getName()).log(Level.SEVERE, null, ex);
            throw new NetworkException("Erreur lors de la creation du local user",ex);
        }
    }
    /**
    * Gère l'envoi d'un signal Hello pour confirmation une notification de connexion.
    * L'utilisateur qui reçoit un <i>Hello Request</i> repond en unicast avec message
    * <i>Hello Reply</i>. 
    * @see Hello 
    * @param rAdress    Adresse IP de l'utilisateur distant destinataire du Hello Reply
    * @param reply      Demande de réponse <code>false</code>
    * @throws NetworkException 
    */
    @Override
    public void sendHelloUnicast(InetAddress rAdress, boolean reply) throws NetworkException {
        try {
            if(rAdress==null) throw new NetworkException("Adresse distante inconnue lors de l'envoi d'un HelloUnicats"+rAdress);
            udpSender.sendHelloUnicast(lUser.getUsername(),rAdress, reply);
        } catch (UDPSenderException ex) {
            Logger.getLogger(ChatNetwork.class.getName()).log(Level.SEVERE, null, ex);
            throw new NetworkException("Adresse distante inconnue lors de l'envoi d'un ChatMessage"+rAdress);
        }
    }
    /**
    * Gère l'envoi d'un signal Bye pour la notification de deconnexion.
    * @see Bye 
    * @throws NetworkException 
    */
    @Override
    public void sendBye() throws NetworkException {
        try {
            mListUser.removeAll();
            System.out.println("Deconnexion du localUser "+lUser.getUsername());
            udpSender.sendBye(lUser.getUsername());
        } catch (UDPSenderException ex) {
            Logger.getLogger(ChatNetwork.class.getName()).log(Level.SEVERE, null, ex);
            throw new NetworkException("Erreur lors l'envoi d'un bye",ex);
        }
    }
    /**
    * Gère l'envoi d'un signal Wizz à un utlisateur distant.
    * @see Wizz
    * @param remoteLogin      Login de l'utilisateur distant
    * @throws NetworkException 
    */
    @Override
    public void sendWizz(String remoteLogin) throws NetworkException {
        try {
            InetAddress rAddress = mListUser.getAddressByName(remoteLogin);
            if(rAddress==null) throw new NetworkException("Adresse distante inconnue lors de l'envoi d'un Wizz"+rAddress);
            udpSender.sendWizz(lUser.getUsername(), rAddress);
        } catch (UDPSenderException ex) {
            Logger.getLogger(ChatNetwork.class.getName()).log(Level.SEVERE, null, ex);
            throw new NetworkException("Erreur lors de l'envoi du wizz",ex);
        }
    }
    /**
    * Gère l'envoi d'un signal ChatMessage à un utilisateur distant
    * @see ChatMessage
    * @param login          Login de l'utilisateur local
    * @param messageText    Message texte à envoyer
    * @throws NetworkException 
    */
    @Override
    public void sendChatMessage(String login, String messageText) throws NetworkException {
        try {
            //Recuperation de l'adresse distante 
           InetAddress rAddress = mListUser.getAddressByName(login);
           if(rAddress==null) throw new NetworkException("Adresse distante inconnue lors de l'envoi d'un ChatMessage"+rAddress);
           System.out.println("Envoi Message:\n"+lUser.getUsername()+">"+login+":"+messageText);
            udpSender.sendChatMessage(lUser.getUsername(), rAddress, messageText);
        } catch (UDPSenderException ex) {
            Logger.getLogger(ChatNetwork.class.getName()).log(Level.SEVERE, null, ex);
            throw new NetworkException("Erreur lors l'envoi d'un message",ex.getCause());
        }
    }
    /**
    * Gère l'envoi d'un signal FileRequest à un utilisateur distant
    * @see FileRequest
    * @param login          Login de l'utilisateur local
    * @param file           Nom du fichier à envoyer
    * @param fileId         Identifiant du fichier à envoyer
    * @param size           taille du fichier à envoyer
    * @throws NetworkException 
    */
    @Override
    public void sendFileRequest(String login,PathFile file, FileId fileId, long size) throws NetworkException {
        try {
            InetAddress rAddress = mListUser.getAddressByName(login);
            if(rAddress==null) throw new NetworkException("Adresse distante inconnue lors de l'envoi d'un FileRequest"+rAddress);
            fileSended.addElement(login, new FileRequest(lUser.getUsername(), file, fileId, size));
            System.out.println("Demande transfert de fichier:\n"+lUser.getUsername()+">"+login+"\n");
            udpSender.sendFileRequest(lUser.getUsername(), rAddress, file, fileId, size);
        } catch (UDPSenderException ex) {
            Logger.getLogger(ChatNetwork.class.getName()).log(Level.SEVERE, null, ex);
            throw new NetworkException("Erreur lors l'envoi d'un FileRequest",ex);
        } catch (UnknownHostException ex) {
            Logger.getLogger(ChatNetwork.class.getName()).log(Level.SEVERE, null, ex);
            throw new NetworkException("Erreur lors de la creation d'un Filerequest",ex);
        }   
    }
    /**
    * Gère l'envoi d'un signal FileResponse à un utilisateur distant
    * @see FileResponse
    * @see FileId
    * 
    * @param login          Login de l'utilisateur local
    * @param jAccept        Reponse à la demande de transfert
    *                      <code>true</code> si utilisateur accepte de recevoir un fichier
    *                      <code>false</code> sinon
    * @throws NetworkException 
    */
    @Override
    public void sendFileResponse(String login,boolean jAccept) throws NetworkException {
        try {
            //Recuperation de l'adresse distante   
            InetAddress rAddress = mListUser.getAddressByName(login);
            if(rAddress==null) throw new NetworkException("Adresse distante inconnue lors de l'envoi d'un FileResponse"+rAddress);
            FileRequest request = fileReceived.getRequestByName(login);
            System.out.println("Reponse tranfert de fichier :\n"+lUser.getUsername()+">"+login+" FileID: "+request.getFileId()+"\n");
            udpSender.sendFileResponse(lUser.getUsername(),rAddress, jAccept,request);
            fileReceived.removeElement(login);

        } catch (UDPSenderException ex) {
            Logger.getLogger(ChatNetwork.class.getName()).log(Level.SEVERE, null, ex);
            throw new NetworkException("Erreur lors de l'envoi d'un Fileresponse",ex);

        }
    }
    /**
    * Gère l'envoi d'un signal Presence pour la notification de status
    * @see Presence
    * @param show           Status de l'utilisateur
    * @throws UDPSenderException    
    */
    @Override
    public void sendPresence(Status show) throws NetworkException{
        try {
            udpSender.sendPresence(lUser.getUsername(), show);
        } catch (UDPSenderException ex) {
            Logger.getLogger(ChatNetwork.class.getName()).log(Level.SEVERE, null, ex);
            throw new NetworkException("Erreur lors de l'envoi d'une Presence", ex);
        }
    }
    /**
     /**
    * Gère l'envoi d'un signal Composing pour la notification d'ecriture 
    * @see Composing
    * @param login          Login de l'utilisateur local
    * @param isWriting   <code>true</code> si utilisateur ecrit 
    *                  <code>false</code> sinon
    */
    @Override
    public void sendComposing(String login,boolean isWriting)throws NetworkException{
        try {
            //Recuperation de l'adresse distante   
            InetAddress rAddress = mListUser.getAddressByName(login);
            if(rAddress==null) throw new NetworkException("Adresse distante inconnue lors de l'envoi d'un FileResponse"+rAddress);
            udpSender.sendComposing(lUser.getUsername(), rAddress,isWriting);
        } catch (UDPSenderException ex) {
            Logger.getLogger(ChatNetwork.class.getName()).log(Level.SEVERE, null, ex);
            throw new NetworkException("Erreur lors de l'envoi d'une Presence", ex);
        }
    }

    private void sendFileData(InetAddress addressServer, int port,PathFile path) throws NetworkException {
        try {
            udpSender.sendFileData(addressServer, port,path);
        } catch (UDPSenderException ex) {
            Logger.getLogger(ChatNetwork.class.getName()).log(Level.SEVERE, null, ex);
            throw new NetworkException("Erreur lors du transfert de fichier",ex);
        }
    }
}
